package com.baroxtech.anoterecorder;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.ProgressDialog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.location.LocationManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.ParcelUuid;
import android.provider.Settings;
import android.util.Log;
import android.widget.Toast;

import com.dinuscxj.refresh.RecyclerRefreshLayout;

import java.util.ArrayList;
import java.util.List;

public class ABLEMIDIScanningActivity extends AppCompatActivity {
    private static final String BLE_MIDI_UUID = "03B80E5A-EDE8-4B33-A751-6CE34EC4C700";
    private static final int REQ_CODE_FOR_OPEN_BLE = 1;
    private static final int REQ_CODE_FOR_LOCATION = 2;
    private static final int REQ_CODE_FOR_OPEN_GPS = 3;
    private static final String TAG = "ABLEMIDIScanningActivity";
    private static final long SCANNING_TIMEOUT = 5000;

    private ABLEDevAdapter bleDevAdapter;
    private ProgressDialog progressDialog;
    private BroadcastReceiver bluetoothSwitchStateReceiver;
    private List<ScanFilter> scanFilters;
    private ScanSettings scanSettings;
    private ScanCallback scanCallback;
    private Runnable delayStopScan;
    private boolean scanning;
    private Handler mainHandler;
    private AMIDIDevManager.EventHandler midiDevEventHandler = new AMIDIDevManager.EventHandler() {
        @Override
        public void onEvent(int eventType, final List<AMIDICommand> commands, final AMIDIDev dev) {
            if (dev != null && dev.isUsb()) return;

            switch (eventType) {
                case AMIDIDevManager.EventType.START_CONNECT:
                    progressDialog.setMessage(getResources().getString(R.string.connecting));
                    progressDialog.show();
                    break;
                case AMIDIDevManager.EventType.CONNECT_FAILED:
                    progressDialog.dismiss();
                    if (dev.bleDev != null) {
                        bleDevAdapter.notifyItemChanged(dev.bleDev);
                    }
                    Toast.makeText(ABLEMIDIScanningActivity.this, R.string.connect_failed, Toast.LENGTH_SHORT).show();
                    break;
                case AMIDIDevManager.EventType.CONNECTED:
                    progressDialog.dismiss();
                    bleDevAdapter.notifyItemChanged(dev.bleDev);
                    break;
                case AMIDIDevManager.EventType.DISCONNECTED:
                    bleDevAdapter.notifyItemChanged(dev.bleDev);
                    break;
                case AMIDIDevManager.EventType.DEVICE_REMOVED:
                    int i = bleDevAdapter.remove(dev.bleDev);
                    bleDevAdapter.notifyItemRemoved(i);
                    break;
            }
        }
    };


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ablemidi_scanning);
        // 初始化 ble
        initBLE();
        // 配置 UI
        initView();
        // 初始化 MIDI
        initMIDI();
        // 首次发现
        tryScan();
    }

    private void initBLE() {
        // 判定是否支持 ble
        boolean isSupportBLE = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2
                && getApplicationContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
        if (!isSupportBLE) {
            Toast.makeText(this, R.string.not_support_ble, Toast.LENGTH_LONG).show();
            finish();
            return;
        }

        // 配置设备发现规则
        scanFilters = new ArrayList<>();
        ScanFilter scanFilter = new ScanFilter.Builder().setServiceUuid(ParcelUuid.fromString(BLE_MIDI_UUID)).build();
        scanFilters.add(scanFilter);
        scanSettings = new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_BALANCED)
                .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) // 不是所有的硬件都支持 FIRST_MATCH。需要自己做去重
                .setMatchMode(ScanSettings.MATCH_MODE_STICKY)
                .setNumOfMatches(ScanSettings.MATCH_NUM_MAX_ADVERTISEMENT)
                .setReportDelay(0)
                .build();

        mainHandler = new Handler(Looper.getMainLooper());
        delayStopScan = new Runnable() {
            @Override
            public void run() {
                stopScan();
            }
        };
        scanCallback = new ScanCallback() {
            @SuppressLint("LongLogTag")
            @Override
            public void onScanResult(int callbackType, ScanResult result) {
                super.onScanResult(callbackType, result);
                addScanResult(result);
            }

            @SuppressLint("LongLogTag")
            @Override
            public void onBatchScanResults(List<ScanResult> results) {
                super.onBatchScanResults(results);
                for (ScanResult result : results) {
                    addScanResult(result);
                }
            }

            @SuppressLint("LongLogTag")
            @Override
            public void onScanFailed(int errorCode) {
                Log.e(TAG, "BLE MIDI scanning failed " + errorCode);
                stopScan();
                super.onScanFailed(errorCode);
                // NOTE: 因为文档未更新 6 这个错误码具体哪个版本引进没有深究。表示请求过于频繁
                if (errorCode == 6) {
                    Toast.makeText(ABLEMIDIScanningActivity.this, R.string.too_often, Toast.LENGTH_SHORT).show();
                }
            }
        };

        // 配置蓝牙开关关闭的监听
        bluetoothSwitchStateReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                if (BluetoothAdapter.STATE_OFF == intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0)) {
                    // 蓝牙关闭的时候停止刷新，断开所有的连接
                    stopScan();
                    if (bleDevAdapter != null) {
                        bleDevAdapter.clear(ABLEDevAdapter.CLEAR_ALL);
                        bleDevAdapter.notifyDataSetChanged();
                    }
                    // 弹出一个 tip 提示框
                    showTips(R.string.bluetooth_turnoffed);
                }
            }
        };
        IntentFilter filter = new IntentFilter();
        filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
        registerReceiver(bluetoothSwitchStateReceiver, filter);
    }

    private void initMIDI() {
        AMIDIDevManager.getInstance().addEventHandler(midiDevEventHandler);
    }

    private void initView() {
        // 设置标题栏名字
        setTitle(R.string.ble_midi_dev_manage);
        // 进度对话框
        progressDialog = new ProgressDialog(this);
        progressDialog.setCancelable(false);
        // 初始化下拉刷新组件
        initRefreshLayout();
    }


    private void initRefreshLayout() {
        RecyclerRefreshLayout layout = (RecyclerRefreshLayout) findViewById(R.id.layout_refresh);
        layout.setNestedScrollingEnabled(true);
        layout.setOnRefreshListener(new RecyclerRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                tryScan();
            }
        });
        // 初始化蓝牙 adapter
        bleDevAdapter = new ABLEDevAdapter(new ABLEDevAdapter.EventListener() {
            @Override
            public void onConnect(BluetoothDevice dev) {
                stopScan();
                // 断开之前的设备
                AMIDIDev connectedDev = AMIDIDevManager.getInstance().getConnectedDevice();
                if (connectedDev != null) {
                    if (connectedDev.isUsb()) {
                        // 如果是 USB 设备已经连接，那么提示无法连接。请先断开 USB 设备
                        showTips(R.string.please_disconnect_usb);
                        return;
                    } else {
                        // 否则断开之前的连接
                        AMIDIDevManager.getInstance().disconnect(connectedDev, false);
                    }
                }
                // 连接当前设备
                AMIDIDevManager.getInstance().connect(new AMIDIDev(dev));
            }

            @Override
            public void onDisConnect(BluetoothDevice dev) {
                // 主动断开设备
                stopScan();
                AMIDIDevManager.getInstance().disconnect(new AMIDIDev(dev), false);
            }
        });
        RecyclerView view = (RecyclerView) findViewById(R.id.recycler_view);
        view.setLayoutManager(new LinearLayoutManager(this));
        view.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL)); // 添加分割线
        view.setAdapter(bleDevAdapter);
        // 显示刷新动画
        layout.setRefreshing(true);
    }

    private void showTips(int msg) {
        AlertDialog.Builder builder = new AlertDialog.Builder(ABLEMIDIScanningActivity.this);
        builder.setTitle(R.string.tips).setMessage(msg).setPositiveButton(R.string.ok, null).show();
    }

    private void stopRefresh() {
        RecyclerRefreshLayout layout = (RecyclerRefreshLayout) findViewById(R.id.layout_refresh);
        layout.setRefreshing(false);
        layout.setEnabled(true);
    }

    private void tryScan() {
        RecyclerRefreshLayout layout = (RecyclerRefreshLayout) findViewById(R.id.layout_refresh);
        layout.setEnabled(false);

        // 判定是否需要打开蓝牙
        if (BluetoothAdapter.getDefaultAdapter().isEnabled()) {
            // 检测权限
            checkPermissions();
        } else {
            // 请求用户打开蓝牙
            Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(intent, REQ_CODE_FOR_OPEN_BLE);
        }
    }

    private void checkPermissions() {
        // 检测定位权限是否开启
        int result = checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION);
        if (result == PackageManager.PERMISSION_GRANTED) {
            onLocalPermissionGranted();
        } else {
            String[] deniedPermissions = {Manifest.permission.ACCESS_FINE_LOCATION};
            requestPermissions(deniedPermissions, REQ_CODE_FOR_LOCATION);
        }
    }

    private boolean isGPSOpen() {
        LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
        if (locationManager == null) return false;
        return locationManager.isProviderEnabled(android.location.LocationManager.GPS_PROVIDER);
    }

    private void onLocalPermissionGranted() {
        // M 以上扫描周围蓝牙外设需要开启 gps
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !isGPSOpen()) {
            new AlertDialog.Builder(this)
                    .setTitle(R.string.notify_title)
                    .setMessage(R.string.gps_notify_msg)
                    .setNegativeButton(R.string.cancel,
                            new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    Toast.makeText(ABLEMIDIScanningActivity.this, R.string.open_gps_for_scanning, Toast.LENGTH_LONG).show();
                                    stopRefresh();
                                }
                            })
                    .setPositiveButton(R.string.setting,
                            new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
                                    startActivityForResult(intent, REQ_CODE_FOR_OPEN_GPS);
                                }
                            })

                    .setCancelable(false)
                    .show();

        } else {
            startScan();
        }
    }

    @SuppressLint("LongLogTag")
    private void startScan() {
        if (scanning) return;
        scanning = true;

        Log.d(TAG, "start BLE MIDI scanning");
        BluetoothLeScanner scanner = BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner();
        scanner.flushPendingScanResults(scanCallback);
        scanner.startScan(scanFilters, scanSettings, scanCallback);
        // 延时一段时间后停止
        mainHandler.postDelayed(delayStopScan, SCANNING_TIMEOUT);
    }

    @SuppressLint("LongLogTag")
    private void addScanResult(ScanResult result) {
        // 对结果进行去重
        BluetoothDevice dev = result.getDevice();
        // 添加设备到列表中
        int pos = bleDevAdapter.add(dev);
        if (pos >= 0) {
            Log.d(TAG, "find new BLE MIDI device " + dev.getName() + " -> " + dev.getAddress());
            bleDevAdapter.notifyItemInserted(pos);
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        int toastText = 0;
        switch (requestCode) {
            case REQ_CODE_FOR_OPEN_BLE:
                if (resultCode == RESULT_OK) {
                    checkPermissions();
                } else {
                    toastText = R.string.open_ble_for_scanning;
                }
                break;
            case REQ_CODE_FOR_LOCATION:
                if (resultCode == RESULT_OK) {
                    onLocalPermissionGranted();
                } else {
                    toastText = R.string.grant_fine_for_scanning;
                }
                break;
            case REQ_CODE_FOR_OPEN_GPS:
                // 因为切换了应用，此时 resultCode 会是 RESULT_CANCEL，所以这里不用 resultCode 判定
                if (isGPSOpen()) {
                    startScan();
                } else {
                    toastText = R.string.open_gps_for_scanning;
                }
                break;
            default:
                return;
        }

        if (toastText != 0) {
            Toast.makeText(this, toastText, Toast.LENGTH_LONG).show();
            stopRefresh();
        }
    }

    @SuppressLint("LongLogTag")
    private void stopScan() {
        if (!scanning) return;
        scanning = false;
        Log.d(TAG, "stop BLE MIDI scanning");

        if (delayStopScan != null) {
            mainHandler.removeCallbacks(delayStopScan);
        }
        if (scanCallback != null) {
            BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner().stopScan(scanCallback);
        }
        stopRefresh();
    }

    @Override
    protected void onDestroy() {
        stopScan();
        AMIDIDevManager.getInstance().removeEventHandler(midiDevEventHandler);
        super.onDestroy();
    }
}